import type { RedisClient } from "./client";

export interface ValueScore {
    value: string;
    score: number;
}

export class SortedSetCommand {
    constructor(client: RedisClient) {
        this.client = client;
    }

    private readonly client: RedisClient;

    async bzmpop(timeout: number, numkeys: number, keyList: string[], opt: "MIN" | "MAX", count: number = 0): Promise<unknown> {
        const args = ["BZMPOP", timeout.toFixed(0), numkeys.toFixed(0), ...keyList, opt];
        if (count > 0) {
            args.push("COUNT", count.toFixed(0));
        }
        const res = await this.client.exec(...args);
        return res;
    }

    async bzpopmax(keyList: string[], timeout: number): Promise<unknown> {
        const res = await this.client.exec("BZPOPMAX", ...keyList, timeout.toFixed(0));
        return res;
    }

    async bzpopmin(keyList: string[], timeout: number): Promise<unknown> {
        const res = await this.client.exec("BZPOPMIN", ...keyList, timeout.toFixed(0));
        return res;
    }

    async zadd(key: string, valueScoreList: ValueScore[], option: "" | "NX" | "XX" = "", cmp: "" | "GT" | "LT" = "", ch: boolean = false, incr: boolean = false): Promise<unknown> {
        const args = ["ZADD", key];
        if (option !== "") {
            args.push(option);
        }
        if (cmp !== "") {
            args.push(cmp);
        }
        if (ch) {
            args.push("CH");
        }
        if (incr) {
            args.push("INCR");
        }
        for (const vs of valueScoreList) {
            args.push(vs.score.toString(), vs.value);
        }
        const res = await this.client.exec(...args);
        return res;
    }

    // return the cardinality (number of elements) of the sorted set, or 0 if key does not exist.
    async zcard(key: string): Promise<number> {
        const res = await this.client.exec("ZCARD", key);
        return res as number;
    }

    // return the number of elements in the specified score range.
    async zcount(key: string, min: number, max: number): Promise<number> {
        const res = await this.client.exec("ZCOUNT", key, min.toString(), max.toString());
        return res as number;
    }

    // return the result of the difference (optionally with their scores
    async zdiff(numkeys: number, keyList: string[], withScores: boolean = false): Promise<string[]> {
        const args = ["ZDIFF", numkeys.toFixed(0), ...keyList];
        if (withScores) {
            args.push("WITHSCORES");
        }
        const res = await this.client.exec(...args);
        return res as string[];
    }

    // return the number of elements in the resulting sorted set at destination.
    async zdiffStore(destKey: string, numkeys: number, keyList: string[]): Promise<number> {
        const res = await this.client.exec("ZDIFFSTORE", destKey, numkeys.toFixed(0), ...keyList);
        return res as number;
    }

    // return the new score of member 
    async zincrBy(key: string, increment: number, value: string): Promise<string> {
        const res = await this.client.exec("ZINCRBY", key, increment.toString(), value);
        return res as string;
    }

    // return the result of intersection (optionally with their scores
    async zinter(numkeys: number, keyList: string[], weightList: number[] = [], aggOpt: "" | "SUM" | "MIN" | "MAX" = "", withScores: boolean = false): Promise<string[]> {
        const args = ["ZINTER", numkeys.toFixed(0), ...keyList];
        if (weightList.length > 0) {
            args.push("WEIGHTS", ...(weightList.map(item => item.toString())));
        }
        if (aggOpt !== "") {
            args.push("AGGREGATE", aggOpt);
        }
        if (withScores) {
            args.push("WITHSCORES");
        }
        const res = await this.client.exec(...args);
        return res as string[];
    }

    // return the number of elements in the resulting intersection.
    async zinterCard(numkeys: number, keyList: string[], limit: number = 0): Promise<number> {
        const args = ["ZINTERCARD", numkeys.toFixed(0), ...keyList];
        if (limit > 0) {
            args.push("LIMIT", limit.toFixed(0));
        }
        const res = await this.client.exec(...args);
        return res as number;
    }

    // return the number of elements in the resulting sorted set at destination.
    async zinterStore(destKey: string, numkeys: number, keyList: string[], weightList: number[] = [], aggOpt: "" | "SUM" | "MIN" | "MAX" = ""): Promise<number> {
        const args = ["ZINTERSTORE", destKey, numkeys.toFixed(0), ...keyList];
        if (weightList.length > 0) {
            args.push("WEIGHTS", ...(weightList.map(item => item.toString())));
        }
        if (aggOpt !== "") {
            args.push("AGGREGATE", aggOpt);
        }
        const res = await this.client.exec(...args);
        return res as number;
    }

    // return the number of elements in the specified score range.
    async zlexCount(key: string, min: number, max: number): Promise<number> {
        const res = await this.client.exec("ZLEXCOUNT", key, min.toString(), max.toString());
        return res as number;
    }

    async zmpop(numkeys: number, keyList: string[], option: "MIN" | "MAX", count: number = 0): Promise<unknown> {
        const args = ["ZMPOP", numkeys.toFixed(0), ...keyList, option];
        if (count > 0) {
            args.push("COUNT", count.toFixed(0));
        }
        const res = await this.client.exec(...args);
        return res;
    }

    async zmscore(key: string, valueList: string[]): Promise<unknown> {
        const res = await this.client.exec("ZMSCORE", key, ...valueList);
        return res;
    }

    // return list of popped elements and scores.
    async zpopMax(key: string, count: number = 0): Promise<string[]> {
        const args = ["ZPOPMAX", key];
        if (count > 0) {
            args.push(count.toFixed(0));
        }
        const res = await this.client.exec(...args);
        return res as string[];
    }

    // return list of popped elements and scores.
    async zpopMin(key: string, count: number = 0): Promise<string[]> {
        const args = ["ZPOPMIN", key];
        if (count > 0) {
            args.push(count.toFixed(0));
        }
        const res = await this.client.exec(...args);
        return res as string[];
    }

    async zrandMember(key: string, count: number = 0, withScores: boolean = false): Promise<unknown> {
        const args = ["ZRANDMEMBER", key];
        if (count > 0) {
            args.push(count.toFixed(0));
        }
        if (withScores) {
            args.push("WITHSCORES");
        }
        const res = await this.client.exec(...args);
        return res
    }

    // return list of elements in the specified range (optionally with their scores,
    async zrange(key: string, start: number, stop: number, byOpt: "" | "BYSCORE" | "BYLEX" = "", rev: boolean = false, offset: number = 0, limit: number = 0, withScores: boolean = false): Promise<string[]> {
        const args = ["ZRANGE", key, start.toString(), stop.toString()];
        if (byOpt !== "") {
            args.push(byOpt);
        }
        if (rev) {
            args.push("REV");
        }
        if (limit > 0) {
            args.push("LIMIT", offset.toFixed(0), limit.toFixed(0));
        }
        if (withScores) {
            args.push("WITHSCORES");
        }
        const res = await this.client.exec(...args);
        return res as string[];
    }

    // return the number of elements in the resulting sorted set.
    async zrangeStore(destKey: string, key: string, min: number, max: number, byOpt: "" | "BYSCORE" | "BYLEX" = "", rev: boolean = false, offset: number = 0, limit: number = 0): Promise<number> {
        const args = ["ZRANGESTORE", destKey, key, min.toString(), max.toString()];
        if (byOpt !== "") {
            args.push(byOpt);
        }
        if (rev) {
            args.push("REV");
        }
        if (limit > 0) {
            args.push("LIMIT", offset.toFixed(0), limit.toFixed(0));
        }
        const res = await this.client.exec(...args);
        return res as number;
    }

    async zrank(key: string, value: string, withScores: boolean = false): Promise<unknown> {
        const args = ["ZRANK", key, value];
        if (withScores) {
            args.push("WITHSCORE");
        }
        const res = await this.client.exec(...args);
        return res;
    }

    // return The number of members removed from the sorted set, 
    async zrem(key: string, valueList: string[]): Promise<number> {
        const res = await this.client.exec("ZREM", key, ...valueList);
        return res as number;
    }

    // return the number of elements removed.
    async zremRangeByLex(key: string, min: string, max: string): Promise<number> {
        const res = await this.client.exec("ZREMRANGEBYLEX", key, min, max);
        return res as number;
    }

    // return the number of elements removed.
    async zremRangeByRank(key: string, start: number, stop: number): Promise<number> {
        const res = await this.client.exec("ZREMRANGEBYRANK", key, start.toFixed(0), stop.toFixed(0));
        return res as number;
    }

    // return the number of elements removed.
    async zremRangeByScore(key: string, min: number, max: number): Promise<number> {
        const res = await this.client.exec("ZREMRANGEBYSCORE", key, min.toString(), max.toString());
        return res as number;
    }

    async zrevRank(key: string, value: string, withScores: boolean = false): Promise<unknown> {
        const args = ["ZREVRANK", key, value];
        if (withScores) {
            args.push("WITHSCORE");
        }
        const res = await this.client.exec(...args);
        return res;
    }

    async zscan(key: string, cursor: number, pattern: string = "", count: number = 0): Promise<unknown> {
        const args = ["ZSCAN", key, cursor.toFixed(0)];
        if (pattern !== "") {
            args.push("MATCH", pattern);
        }
        if (count > 0) {
            args.push("COUNT", count.toFixed(0));
        }
        const res = await this.client.exec(...args);
        return res;
    }

    // return the score of member (a double precision floating point number)
    async zscore(key: string, value: string): Promise<string> {
        const res = await this.client.exec("ZSCORE", key, value);
        return res as string;
    }

    // return the result of union (optionally with their scores
    async zunion(numkeys: number, keyList: string[], weightList: number[] = [], aggOpt: "" | "SUM" | "MIN" | "MAX" = "", withScores: boolean = false): Promise<string[]> {
        const args = ["ZUNION", numkeys.toFixed(0), ...keyList];
        if (weightList.length > 0) {
            args.push("WEIGHTS", ...(weightList.map(item => item.toString())));
        }
        if (aggOpt !== "") {
            args.push("AGGREGATE", aggOpt);
        }
        if (withScores) {
            args.push("WITHSCORES");
        }
        const res = await this.client.exec(...args);
        return res as string[];
    }

    // return the number of elements in the resulting sorted set at destination.
    async zunionStore(destKey: string, numkeys: number, keyList: string[], weightList: number[] = [], aggOpt: "" | "SUM" | "MIN" | "MAX" = ""): Promise<number> {
        const args = ["ZUNIONSTORE", destKey, numkeys.toFixed(0), ...keyList];
        if (weightList.length > 0) {
            args.push("WEIGHTS", ...(weightList.map(item => item.toString())));
        }
        if (aggOpt !== "") {
            args.push("AGGREGATE", aggOpt);
        }
        const res = await this.client.exec(...args);
        return res as number;
    }
}